from paho.mqtt import client as mqtt_client  # MQTT 客户端库，用于实现 MQTT 通信
import random
import time
import logging
import json  # 处理json格式数据

broker = 'broker.emqx.io'  # MQTT代理服务器地址
port = 1883  # MQTT端口号
topic = "python/mqtt"  # 通信主题
client_id = f'python-mqtt-{random.randint(0, 1000)}'  # 客户端id
username = 'emqx'  # 用户名
password = 'public'  # 密码


# 连接服务器
def connect_mqtt():
    def on_connect(client, userdata, flags, rc, properties):
        if rc == 0:
            print("connected to MQTT Broker!")
        else:
            print(f"failed to connect, return code {rc}")

    # 设置连接客户端ID 和 回调API版本
    client = mqtt_client.Client(client_id=client_id, callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2)
    # 设置用户名和密码
    # client.username_pw_set(username, password)
    client.on_connect = on_connect  # 设置连接回调函数
    client.connect(broker, port, keepalive=120)  # 代理到服务器 心跳间隔 120秒
    return client


# 自动重连
FIRST_RECONNECT_DELAY = 1  # 首次重连延迟（s）
RECONNECT_RATE = 2  # 重连延迟递增倍率
MAX_RECONNECT_COUNT = 12  # 最大重连次数
MAX_RECONNECT_DELAY = 60  # 最大重连延迟

FLAG_EXIT = False  # 退出标志


def on_disconnect(client, userdata, rc):
    logging.info(f"与结果代码断开连接: {rc}")
    reconnect_count, reconnect_delay = 0, FIRST_RECONNECT_DELAY
    while reconnect_count < MAX_RECONNECT_COUNT:
        logging.info(f"重新连接{reconnect_count} s...")
        time.sleep(reconnect_delay)

        try:
            client.reconnect()
            logging.info("重连成功")
            return
        except Exception as err:
            logging.error(f"{err}. 重连失败，重试中...")

        reconnect_delay *= RECONNECT_RATE
        reconnect_delay = min(reconnect_delay, MAX_RECONNECT_DELAY)
        reconnect_count += 1
    logging.info(f"在重试 {reconnect_count}次后仍旧失败，退出")
    global FLAG_EXIT
    FLAG_EXIT = True


# 发布消息
def publish(client):
    msg_count = 1
    while not FLAG_EXIT:
        msg_dict = {'msg': msg_count}  # 创建消息字典
        msg = json.dumps(msg_dict)  # 序列化为json字符串
        if not client.is_connected():  # 检查连接状态
            logging.error("publish:MQTT client is not connected!")
            time.sleep(1)
            continue
        result = client.publish(topic, msg)  # 发送消息
        status = result[0]
        if status == 0:
            print(f"send '{msg}' to topic '{topic}'")
        else:
            print(f"failed to send message to topic {topic}")

        msg_count += 1
        if msg_count > 5:
            break

        time.sleep(1)


# 订阅
def subscribe(client: mqtt_client):
    def on_message(client, userdata, msg):
        print(f"received '{msg.payload.decode()}' from '{msg.topic}' topic")

    client.subscribe(topic)  # 订阅指定主题
    client.on_message = on_message  # 设置消息接收和回调函数


# 测试
def run():
    logging.basicConfig(format='%(asctimes - %(levelname)s:%(message)s)', level=logging.DEBUG)
    client = connect_mqtt()  # 连接MQTT代理
    client.on_disconnect = on_disconnect  # 设置断开连接回调
    subscribe(client)  # 订阅主题
    client.loop_start()  # 启动网络循环
    time.sleep(1)  # 等待连接建立
    if client.is_connected():
        publish(client)  # 连接成功发布消息
    else:
        client.loop_stop()  # 连接失败，停止循环

    client.loop_stop()  # 停止网络循环


if __name__ == '__main__':
    run()
